/*
  +----------------------------------------------------------------------+
  | PHP Version 5                                                        |
  +----------------------------------------------------------------------+
  | Copyright (c) 1997-2012 The PHP Group                                |
  +----------------------------------------------------------------------+
  | This source file is subject to version 3.01 of the PHP license,      |
  | that is bundled with this package in the file LICENSE, and is        |
  | available through the world-wide-web at the following url:           |
  | http://www.php.net/license/3_01.txt                                  |
  | If you did not receive a copy of the PHP license and are unable to   |
  | obtain it through the world-wide-web, please send a note to          |
  | license@php.net so we can mail you a copy immediately.               |
  +----------------------------------------------------------------------+
  | Author:                                                              |
  +----------------------------------------------------------------------+
*/

/* $Id: dprofiler.c 2 2012-04-16 13:27:24Z phpdprofiler@gmail.com $ */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "php.h"
#include "php_ini.h"
#include "ext/standard/info.h"
#include "php_dprofiler.h"

#include <sys/time.h>
#include <sys/resource.h>

ZEND_DECLARE_MODULE_GLOBALS(dprofiler);

static ZEND_DLEXPORT void (*_old_zend_execute) (zend_op_array *ops TSRMLS_DC);
static ZEND_DLEXPORT void (*_old_zend_execute_internal) (zend_execute_data *data,int ret TSRMLS_DC);
static zend_op_array * (*_old_zend_compile_file) (zend_file_handle *file_handle,int type TSRMLS_DC);

ZEND_DLEXPORT void dp_execute (zend_op_array *ops TSRMLS_DC);
ZEND_DLEXPORT void dp_execute_internal (zend_execute_data *execute_data,int ret TSRMLS_DC);
zend_op_array * dp_compile_file (zend_file_handle *file_handle,int type TSRMLS_DC);

static void start_profiler(int is_ignore_buildin_func);
static void end_profiler(zend_bool);
static zval *dp_array_current_stacks(char *);
static char *hp_get_function_name(zend_op_array *ops TSRMLS_DC);
static double dp_get_walltime();
static void dp_format(char *ret, char *format, char *script_name, char *str_arg);

#define INIT_PROFILER(is_use_stack)\
	HashTable     *ht;\
void          *data;\
zval          *stacks;\
zval          *emstacks;\
zval          *stat;\
ulong         index;\
int           use_stack=is_use_stack;\
long		  stat_mu;\
long 		  stat_mpu;\
double 		  stat_wt;\


#define BEGIN_PROFILER							\
stacks = dp_array_current_stacks(func);														\
if(zend_hash_find(Z_ARRVAL_P(stacks), func, strlen(func)+1, &data) == SUCCESS){				\
	stat = *(zval **)data;																	\
	if(zend_hash_find(Z_ARRVAL_P(stat), "count", sizeof("count"), &data) == SUCCESS){		\
		ZVAL_LONG(*(zval**)data, Z_LVAL_PP((zval**)data) + 1);								\
	}																						\
}else{																						\
	MAKE_STD_ZVAL(stat);																	\
	array_init(stat);																		\
	add_assoc_long(stat, "count", 1);														\
	if (use_stack){\
		MAKE_STD_ZVAL(emstacks);																\
		array_init(emstacks);																	\
		add_assoc_zval(stat, "stacks", emstacks);												\
	}\
	\
	add_assoc_zval(stacks, func, stat);														\
}																							\
\
stat_mu = XG_MEMORY_USAGE();\
stat_mpu = XG_MEMORY_PEAK_USAGE();\
stat_wt = dp_get_walltime();\
if (use_stack){\
	add_next_index_string(DPROFILER_G(stack), func, 1);							\
}\


#define END_PROFILER\
	if(use_stack){\
		ht = Z_ARRVAL_P(DPROFILER_G(stack));\
		zend_hash_internal_pointer_end(ht);\
		zend_hash_get_current_key_ex(ht, NULL, 0, &index, 0, NULL);\
		zend_hash_del_key_or_index(ht, NULL, 0, index, HASH_DEL_INDEX);\
	}\
stat_mu = XG_MEMORY_USAGE() - stat_mu;\
stat_mpu = XG_MEMORY_PEAK_USAGE() - stat_mpu;\
stat_wt = dp_get_walltime() - stat_wt;\
add_assoc_long(stat, "mu", stat_mu);\
add_assoc_long(stat, "mpu", stat_mpu);\
add_assoc_double(stat, "wt", stat_wt);\


/* {{{ dprofiler_functions[]
 *
 * Every user visible function must have an entry in dprofiler_functions[].
 */
	const zend_function_entry dprofiler_functions[] = {
		PHP_FE(dprofiler_enable,	NULL)
			PHP_FE(dprofiler_disable,	NULL)
			PHP_FE_END	/* Must be the last line in dprofiler_functions[] */
	};
/* }}} */

/* {{{ dprofiler_module_entry
*/
zend_module_entry dprofiler_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
	STANDARD_MODULE_HEADER,
#endif
	"dprofiler",
	dprofiler_functions,
	PHP_MINIT(dprofiler),
	PHP_MSHUTDOWN(dprofiler),
	PHP_RINIT(dprofiler),
	PHP_RSHUTDOWN(dprofiler),
	PHP_MINFO(dprofiler),
#if ZEND_MODULE_API_NO >= 20010901
	DPROF_VERSION, /* Replace with version number for your extension */
#endif
	STANDARD_MODULE_PROPERTIES
};
/* }}} */

#ifdef COMPILE_DL_DPROFILER
ZEND_GET_MODULE(dprofiler)
#endif

	/* {{{ PHP_INI
	*/
PHP_INI_BEGIN()
	STD_PHP_INI_ENTRY("dprofiler.auto_profiler",      "0", PHP_INI_ALL, OnUpdateLong, auto_profiler, zend_dprofiler_globals, dprofiler_globals)
	STD_PHP_INI_ENTRY("dprofiler.timelimit",      "0", PHP_INI_ALL, OnUpdateLong, timelimit, zend_dprofiler_globals, dprofiler_globals)
	STD_PHP_INI_ENTRY("dprofiler.ignore_buildin_function",      "1", PHP_INI_ALL, OnUpdateLong, ignore_buildin_function, zend_dprofiler_globals, dprofiler_globals)
	STD_PHP_INI_ENTRY("dprofiler.outdir", "", PHP_INI_ALL, OnUpdateString, outdir, zend_dprofiler_globals, dprofiler_globals)
	STD_PHP_INI_ENTRY("dprofiler.name", "", PHP_INI_ALL, OnUpdateString, name, zend_dprofiler_globals, dprofiler_globals)
PHP_INI_END()
	/* }}} */

	/* {{{ php_dprofiler_init_globals
	*/
static void php_dprofiler_init_globals(zend_dprofiler_globals *dprofiler_globals)
{
	dprofiler_globals->auto_profiler = 0;
	dprofiler_globals->timelimit = 0;
	dprofiler_globals->ignore_buildin_function = 1;
	dprofiler_globals->outdir = "";
	dprofiler_globals->name = "";
}
/* }}} */


/* {{{ PHP_MINIT_FUNCTION
*/
PHP_MINIT_FUNCTION(dprofiler)
{
	REGISTER_INI_ENTRIES();

	_old_zend_execute = zend_execute;
	_old_zend_execute_internal = zend_execute_internal;
	_old_zend_compile_file = zend_compile_file;

	return SUCCESS;
}
/* }}} */


/* {{{ PHP_MSHUTDOWN_FUNCTION
*/
PHP_MSHUTDOWN_FUNCTION(dprofiler)
{
	UNREGISTER_INI_ENTRIES();

	return SUCCESS;
}
/* }}} */

/* {{{ PHP_RINIT_FUNCTION
*/
PHP_RINIT_FUNCTION(dprofiler)
{
	if (DPROFILER_G(auto_profiler) == 100){
		start_profiler(DPROFILER_G(ignore_buildin_function));
	}else if (DPROFILER_G(auto_profiler) > 0){
		if (rand() % 100 < DPROFILER_G(auto_profiler))
			start_profiler(DPROFILER_G(ignore_buildin_function));
	}

	return SUCCESS;
}
/* }}} */

/* {{{ PHP_RSHUTDOWN_FUNCTION
*/
PHP_RSHUTDOWN_FUNCTION(dprofiler)
{
	end_profiler(1);

	return SUCCESS;
}
/* }}} */

/* {{{ PHP_MINFO_FUNCTION
*/
PHP_MINFO_FUNCTION(dprofiler)
{
	php_info_print_table_start();
	php_info_print_table_header(2, "dprofiler support", "enabled");
	php_info_print_table_row(2, "dprofiler module version", DPROF_VERSION);
	php_info_print_table_row(2, "dprofiler.auto_profiler", "0-100 0:not start 100:start [0-100]:rand()%100 < auto_profiler start");
	php_info_print_table_row(2, "dprofiler.name format", "%f %a %t %p f:script_name a:query_string t:unixtime p:pid");
	php_info_print_table_row(2, "dprofiler.timelimit", "unit:microtime greater than timelimit will be save");
	php_info_print_table_end();

	DISPLAY_INI_ENTRIES();
}
/* }}} */


PHP_FUNCTION(dprofiler_enable)
{
	zend_bool ignore;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "b", &ignore) == FAILURE) {
		return;
	}

	start_profiler(ignore);
}


static void start_profiler(int is_ignore_buildin_func)
{
	char *arg = NULL;
	int arg_len, len;
	char *strg;

	DPROFILER_G(enable) = 1;

	// init variable
	zval * stats;
	MAKE_STD_ZVAL(stats);
	array_init(stats);
	DPROFILER_G(dp_stats) = stats;

	zval * stack,*stacks;

	MAKE_STD_ZVAL(stack);
	MAKE_STD_ZVAL(stacks);

	array_init(stack);
	array_init(stacks);

	add_assoc_long(stats, "mu", XG_MEMORY_USAGE());
	add_assoc_long(stats, "mpu", XG_MEMORY_PEAK_USAGE());
	add_assoc_double(stats, "wt", dp_get_walltime());
	add_assoc_zval(stats, "stacks", stacks);

	DPROFILER_G(stack) = stack;

	// proxy opcode execute func
	zend_execute = dp_execute;
	zend_compile_file = dp_compile_file;

	if (!is_ignore_buildin_func)
		zend_execute_internal = dp_execute_internal;
	else
		zend_execute_internal = _old_zend_execute_internal;
}

static void end_profiler(zend_bool is_save)
{
	void          *data;
	zval          *dprofiler_stats;
	FILE          *fp;
	zval          *sret;
	double        wt=0;

	char          *script_name = "";
	char          *str_arg = "";
	char          filename[256] = {0};
	char          name[256] = {0};

	if (DPROFILER_G(enable) == 1){
		DPROFILER_G(enable) = 0;
		dprofiler_stats = DPROFILER_G(dp_stats);

		if(zend_hash_find(Z_ARRVAL_P(dprofiler_stats), "mu", sizeof("mu"), &data) == SUCCESS){
			ZVAL_LONG(*(zval**)data, XG_MEMORY_USAGE() - Z_LVAL_PP((zval**)data) );
		}
		if(zend_hash_find(Z_ARRVAL_P(dprofiler_stats), "mpu", sizeof("mpu"), &data) == SUCCESS){
			ZVAL_LONG(*(zval**)data, XG_MEMORY_PEAK_USAGE() - Z_LVAL_PP((zval**)data));
		}
		if(zend_hash_find(Z_ARRVAL_P(dprofiler_stats), "wt", sizeof("wt"), &data) == SUCCESS){
			wt = dp_get_walltime() - Z_DVAL_PP((zval**)data);
			ZVAL_DOUBLE(*(zval**)data, wt);
		}

		//zend_call_method(NULL, NULL, NULL, "print_r", sizeof("print_r")-1, &sret, 1, dprofiler_stats, NULL TSRMLS_CC);
		//save to file
		if( is_save &&
				wt > DPROFILER_G(timelimit)*1000 &&
				strlen(DPROFILER_G(outdir)) > 0 &&
				strlen(DPROFILER_G(name)) > 0 &&
				zend_call_method(NULL, NULL, NULL, "serialize", sizeof("serialize")-1, &sret, 1, dprofiler_stats, NULL TSRMLS_CC)){
			// get SCRIPT_NAME
			if(zend_hash_find(&EG(symbol_table), "_SERVER", sizeof("_SERVER"), &data) == SUCCESS){
				if(zend_hash_find(Z_ARRVAL_PP((zval **)data), "SCRIPT_NAME", sizeof("SCRIPT_NAME"), &data) == SUCCESS){
					script_name = Z_STRVAL_PP((zval **)data);
				}
			}

			// get QUERY_STRING
			if(zend_hash_find(&EG(symbol_table), "_SERVER", sizeof("_SERVER"), &data) == SUCCESS){
				if(zend_hash_find(Z_ARRVAL_PP((zval **)data), "QUERY_STRING", sizeof("QUERY_STRING"), &data) == SUCCESS){
					str_arg = Z_STRVAL_PP((zval **)data);
				}
			}

			dp_format(name, DPROFILER_G(name), script_name, str_arg);

			snprintf(filename, 256, "%s/%s", DPROFILER_G(outdir), name);
			fp = fopen(filename, "w");

			if (fp){
				fwrite(Z_STRVAL_P(sret), Z_STRLEN_P(sret), 1, fp);
				fflush(fp);
			}

			fclose(fp);
		}

		// free mem
		zval_ptr_dtor(&DPROFILER_G(dp_stats));
		zval_ptr_dtor(&DPROFILER_G(stack));
		DPROFILER_G(dp_stats) = NULL;
		DPROFILER_G(stack) = NULL;

		zend_execute = _old_zend_execute;
		zend_execute_internal = _old_zend_execute_internal;
		zend_compile_file = _old_zend_compile_file;
	}
}


PHP_FUNCTION(dprofiler_disable)
{
	zend_bool is_save = 0;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|b", &is_save) == FAILURE) {
		return;
	}

	end_profiler(is_save);
}


ZEND_DLEXPORT void dp_execute (zend_op_array *ops TSRMLS_DC)
{
	char          *func = NULL;
	INIT_PROFILER(1);

	func = hp_get_function_name(ops TSRMLS_CC);
	if (func == NULL) {
		_old_zend_execute(ops TSRMLS_CC);
		return;
	}

	// start profiler
	BEGIN_PROFILER;

	_old_zend_execute(ops TSRMLS_CC);

	if(zend_hash_find(Z_ARRVAL_P(stat), "cpu", sizeof("cpu"), &data) == SUCCESS){
		ZVAL_LONG(*(zval**)data, Z_LVAL_PP((zval**)data) + 1);
	}
	// end profiler pop 
	END_PROFILER;

	efree(func);
}

static zval *dp_array_current_stacks(char * strfunc)
{
	HashTable     *ht;
	void          *data;
	zval          *stacks;
	zval          *stack;
	zval          *func;

	stack = DPROFILER_G(stack);

	if(zend_hash_find(Z_ARRVAL_P(DPROFILER_G(dp_stats)), "stacks", sizeof("stacks"), &data) == SUCCESS){
		stacks = *(zval **)data;
	}

	if (stack != NULL)
	{
		zend_hash_internal_pointer_reset(Z_ARRVAL_P(stack));
		while(zend_hash_get_current_data(Z_ARRVAL_P(stack), &data) == SUCCESS){
			func = *(zval **)data;
			if (strcmp(strfunc, Z_STRVAL_P(func)) == 0)
				break;// recursion return

			// func => stacks
			if (zend_hash_find(Z_ARRVAL_P(stacks),Z_STRVAL_P(func), Z_STRLEN_P(func)+1, &data) == SUCCESS){
				if (zend_hash_find(Z_ARRVAL_P(*(zval **)data),"stacks", sizeof("stacks"), &data) == SUCCESS){
					stacks = *(zval **)data;
				}
			}

			zend_hash_move_forward(Z_ARRVAL_P(stack));
		}
	}

	return stacks;
}


ZEND_DLEXPORT void dp_execute_internal (zend_execute_data *execute_data,int ret TSRMLS_DC)
{
	zend_execute_data *current_data;
	char             *func = NULL;
	zend_function     *fbc = execute_data->function_state.function;
	int    hp_profile_flag = 1;
	INIT_PROFILER(0);

	current_data = EG(current_execute_data);
	func = hp_get_function_name(current_data->op_array TSRMLS_CC);

	if (func) {
		BEGIN_PROFILER;
	}

	if (!_old_zend_execute_internal) {
		execute_internal(execute_data, ret TSRMLS_CC);
	} else {
		/* call the old override */
		_old_zend_execute_internal(execute_data, ret TSRMLS_CC);
	}

	if (func) {
		END_PROFILER;
		efree(func);
	}
}



zend_op_array * dp_compile_file (zend_file_handle *file_handle,int type TSRMLS_DC)
{
	char           *filename;
	char           *func;
	int             len;
	zend_op_array  *ret;
	int             hp_profile_flag = 1;

	INIT_PROFILER(0);

	filename = file_handle->filename;
	len      = strlen("load") + strlen(filename) + 3;
	func      = (char *)emalloc(len);
	snprintf(func, len, "load::%s", filename);

	BEGIN_PROFILER;
	ret = _old_zend_compile_file(file_handle, type TSRMLS_CC);
	END_PROFILER;

	if (func){
		efree(func);
	}

	return ret;
}

/**
 * from xhprof
 */
static char *hp_get_function_name(zend_op_array *ops TSRMLS_DC) {
	zend_execute_data *data;
	char              *func = NULL;
	char              *cls = NULL;
	char              *ret = NULL;
	int                len;
	zend_function      *curr_func;

	data = EG(current_execute_data);

	if (data) {
		/* shared meta data for function on the call stack */
		curr_func = data->function_state.function;

		/* extract function name from the meta info */
		func = curr_func->common.function_name;

		if (func) {
			/* previously, the order of the tests in the "if" below was
			 ** flipped, leading to incorrect function names in profiler
			 ** reports. When a method in a super-type is invoked the
			 ** profiler should qualify the function name with the super-type
			 ** class name (not the class name based on the run-time type
			 ** of the object.
			 **/
			if (curr_func->common.scope) {
				cls = curr_func->common.scope->name;
			} else if (data->object) {
				cls = Z_OBJCE(*data->object)->name;
			}

			if (cls) {
				len = strlen(cls) + strlen(func) + 10;
				ret = (char*)emalloc(len);
				snprintf(ret, len, "%s::%s", cls, func);
			} else {
				ret = estrdup(func);
			}
		} else {
			long     curr_op;
			int      desc_len;
			char    *desc;
			int      add_filename = 0;

			/* we are dealing with a special directive/function like
			 ** include, eval, etc.
			 **/
			curr_op = data->opline->op2.u.constant.value.lval;
			switch (curr_op) {
				case ZEND_EVAL:
					func = "eval";
					break;
				case ZEND_INCLUDE:
					func = "include";
					add_filename = 1;
					break;
				case ZEND_REQUIRE:
					func = "require";
					add_filename = 1;
					break;
				case ZEND_INCLUDE_ONCE:
					func = "include_once";
					add_filename = 1;
					break;
				case ZEND_REQUIRE_ONCE:
					func = "require_once";
					add_filename = 1;
					break;
				default:
					func = "???_op";
					break;
			}

			/* For some operations, we'll add the filename as part of the function
			 ** name to make the reports more useful. So rather than just "include"
			 ** you'll see something like "run_init::foo.php" in your reports.
			 **/
			if (add_filename){
				char *filename;
				int   len;
				filename = (curr_func->op_array).filename;
				len      = strlen(func) + strlen(filename) + 3;
				ret      = (char *)emalloc(len);
				snprintf(ret, len, "%s::%s", func,filename);
			} else {
				ret = estrdup(func);
			}
		}
	}

	return ret;
}


static double dp_get_walltime()
{
	/**
	struct rusage ru;

	getrusage(RUSAGE_SELF, &ru);

	return ru.ru_utime.tv_sec+ru.ru_stime.tv_sec+
		(ru.ru_utime.tv_usec + ru.ru_stime.tv_usec)/10e6;
	**/

	struct timeval tv;
	gettimeofday(&tv, NULL);

	return tv.tv_sec + tv.tv_usec / 1000000.00;
}


/**
 * format: f script_name a argments t time p pid
 */
static void dp_format(char *ret, char *format, char *script_name, char *str_arg)
{
	int pos = 0, len=0;
	char c;

	while(*format)
	{
		if (*format == '%'){
			format++;
			switch(*format){
				case 'f':
					if (script_name != NULL && strlen(script_name) > 0)
					{
						len = strlen(script_name);
						if (len > 64)len = 64;
						strncpy(ret+pos, script_name, len);
						pos += len;
					}
					break;
				case 'a':
					if (str_arg != NULL && strlen(str_arg) > 0)
					{
						len = strlen(str_arg);
						if (len > 96)len = 96;
						strncpy(ret+pos, str_arg, len);
						pos += len;
					}
					break;
				case 't':
					pos += sprintf(ret+pos, "%ld", time(NULL));
					break;
				case 'p':
					pos += sprintf(ret+pos, "%ld", getpid());
					break;
			}
		}else{
			ret[pos++] = *format;
		}

		format++;
	}

	while(*ret){
		c=*ret;
		if (!((c >='0' && c <='9') || (c>='a' && c<='z') || (c>='A' && c<='Z') || c=='.'))
			*ret = '_';
		ret++;
	}
}


/*
 * Local variables:
 * tab-width: 4
 * c-basic-offset: 4
 * End:
 * vim600: noet sw=4 ts=4 fdm=marker
 * vim<600: noet sw=4 ts=4
 */
